package com.sys.midware.httpClient.crawler;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.SocketConfig;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;



/**
 * http 实用类封装,不提供失败重试机制，需调用者自行保证<br>
 * 只支持get，head请求，post请求<br>
 * 默认最多支持300个并发，超时时间5000ms,UA是chrome 31
 * 
 * @author mobing
 * @version 2.0
 */
public class HttpClient {

    private CloseableHttpClient httpclient;

    private Handler handler;

    private PoolingHttpClientConnectionManager connManager;

    private HttpHost proxy;

    private Map<String, Object> headers;

    private Map<String, Object> entity;

    private String charset = "iso-8859-1";

    private static final int DEFAULT_MAX = 300;

    private static final int DEFAULT_TIME_OUT = 1000;

    private static final String DEFAULT_UA = UA.CHROME_31.getValue();

    public HttpClient() {
        this(DEFAULT_UA);
    }

    public HttpClient(String userAgent) {
        this(userAgent, new DefaultRedirectStrategy());
    }

    public HttpClient(String userAgent, RedirectStrategy strategy) {
        if (userAgent == null) {
            throw new IllegalArgumentException("userAgent can't be null");
        }
        if (strategy == null) {
            throw new IllegalArgumentException("strategy can't be null");
        }
        // 禁用Nagle
        SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
        connManager = new PoolingHttpClientConnectionManager();
        connManager.setMaxTotal(DEFAULT_MAX);
        connManager.setDefaultMaxPerRoute(DEFAULT_MAX);
        connManager.setDefaultSocketConfig(socketConfig);
        connManager.closeIdleConnections(30, TimeUnit.SECONDS);

        httpclient = HttpClientBuilder.create().setRedirectStrategy(strategy)
                .addInterceptorFirst(new ResponseInterceptor()).setConnectionManager(connManager)
                .setUserAgent(userAgent).build();

        handler = new Handler();
    }

    // public HttpResult get(String url, int timeoutMills)
    // throws ClientProtocolException, IOException {
    // return get(url, timeoutMills, null);
    // }

    // public HttpResult get(String url, int timeoutMills,String proxyAddr,int
    // proxyPort)
    // throws ClientProtocolException, IOException {
    // HttpHost host = new HttpHost(proxyAddr, proxyPort);
    // HttpGet get = new HttpGet(url);
    // get.setConfig(initConfig(timeoutMills,host));
    // return httpclient.execute(get, handler);
    // }

    // public HttpResult get(String url, int timeoutMills,
    // Map<String, Object> headers) throws ClientProtocolException,
    // IOException {
    // HttpGet get = null;
    // try {
    // get = new HttpGet(url);
    // if (MapUtils.isNotEmpty(headers)) {
    // List<Header> list = new LinkedList<Header>();
    // for (Map.Entry<String, Object> entry : headers.entrySet()) {
    // Header header = new BasicHeader(entry.getKey(),
    // ObjectUtils.toString(entry.getValue()));
    // list.add(header);
    // }
    // get.setHeaders(list.toArray(new Header[list.size()]));
    // }
    // get.setConfig(initConfig(timeoutMills));
    // return httpclient.execute(get, handler);
    // } finally {
    // if (get != null) {
    // get.releaseConnection();
    // }
    // }
    // }

    public HttpResult get(String url, int timeoutMills) throws ClientProtocolException, IOException {
        HttpGet get = null;
        try {
            get = new HttpGet(url);
            if (MapUtils.isNotEmpty(headers)) {
                List<Header> list = new LinkedList<Header>();
                for (Map.Entry<String, Object> entry : headers.entrySet()) {
                    Header header = new BasicHeader(entry.getKey(), ObjectUtils.toString(entry.getValue()));
                    list.add(header);
                }
                get.setHeaders(list.toArray(new Header[list.size()]));
            }
            get.setConfig(initConfig(timeoutMills));
            return httpclient.execute(get, handler);
        } finally {
            if (get != null) {
                get.releaseConnection();
            }
        }
    }

    public HttpResult get(String url) throws ClientProtocolException, IOException {
        return get(url, DEFAULT_TIME_OUT);
    }

    
    /**
    *
    * @see http://hc.apache.org/httpcomponents-client-4.3.x/examples.html
    * @param url
    * @param headers
    * @param entity
    * @param timeoutMills
    * @return
    * @throws ClientProtocolException
    * @throws IOException
    */
   public HttpResult post(String url,int timeoutMills)
           throws ClientProtocolException, IOException {
       HttpPost post = null;
       try {
           post = new HttpPost(url);
           if (MapUtils.isNotEmpty(headers)) {
               List<Header> list = new LinkedList<Header>();
               for (Map.Entry<String, Object> entry : headers.entrySet()) {
                   Header header = new BasicHeader(entry.getKey(),
                           ObjectUtils.toString(entry.getValue()));
                   list.add(header);
               }
               post.setHeaders(list.toArray(new Header[list.size()]));
           }

           if (MapUtils.isNotEmpty(entity)) {
               List<BasicNameValuePair> parameters = new ArrayList<BasicNameValuePair>();
               for (Map.Entry<String, Object> entry : entity.entrySet()) {
                   parameters.add(new BasicNameValuePair(entry.getKey(),entry.getValue()==null?"":entry.getValue().toString()));
               }
               
               if (StringUtils.isBlank(charset)) {
                   post.setEntity(new UrlEncodedFormEntity(parameters));
               } else {
                   post.setEntity(new UrlEncodedFormEntity(parameters, Charset
                           .forName(charset)));
               }

           }

           post.setConfig(initConfig(timeoutMills));
           return httpclient.execute(post, handler);
       } finally {
           if (post != null) {
               post.releaseConnection();
           }
       }
   }
   
   /**
    * @see http://hc.apache.org/httpcomponents-client-4.3.x/examples.html
    * @param url
    * @param entity
    * @return
    * @throws ClientProtocolException
    * @throws IOException
    */
   public HttpResult post(String url)
           throws ClientProtocolException, IOException {
       return post(url,DEFAULT_TIME_OUT);
   }
    
//    /**
//     *
//     * @see http://hc.apache.org/httpcomponents-client-4.3.x/examples.html
//     * @param url
//     * @param headers
//     * @param entity
//     * @param timeoutMills
//     * @return
//     * @throws ClientProtocolException
//     * @throws IOException
//     */
//    public HttpResult post(String url, Map<String, Object> headers, Map<String, Object> entity, int timeoutMills)
//            throws ClientProtocolException, IOException {
//        return post(url, headers, entity, timeoutMills, null);
//    }
//
//    /**
//     *
//     * @see http://hc.apache.org/httpcomponents-client-4.3.x/examples.html
//     * @param url
//     * @param headers
//     * @param entity
//     * @param timeoutMills
//     * @return
//     * @throws ClientProtocolException
//     * @throws IOException
//     */
//    public HttpResult post(String url, Map<String, Object> headers, Map<String, Object> entity, int timeoutMills,
//            String charset) throws ClientProtocolException, IOException {
//        HttpPost post = null;
//        try {
//            post = new HttpPost(url);
//            if (MapUtils.isNotEmpty(headers)) {
//                List<Header> list = new LinkedList<Header>();
//                for (Map.Entry<String, Object> entry : headers.entrySet()) {
//                    Header header = new BasicHeader(entry.getKey(), ObjectUtils.toString(entry.getValue()));
//                    list.add(header);
//                }
//                post.setHeaders(list.toArray(new Header[list.size()]));
//            }
//
//            if (MapUtils.isNotEmpty(entity)) {
//                StringBuilder sb = new StringBuilder();
//                for (Map.Entry<String, Object> entry : entity.entrySet()) {
//                    String key = entry.getKey();
//                    Object value = entry.getValue();
//                    String str = ObjectUtils.toString(value);
//                    sb.append("&").append(key);
//                    if (StringUtils.isNotBlank(str)) {
//                        sb.append("=").append(key).append(str);
//                    }
//                }
//
//                if (StringUtils.isBlank(charset)) {
//                    post.setEntity(new StringEntity(sb.substring(1)));
//                } else {
//                    post.setEntity(new StringEntity(sb.substring(1), Charset.forName(charset)));
//                }
//
//            }
//
//            post.setConfig(initConfig(timeoutMills));
//            return httpclient.execute(post, handler);
//        } finally {
//            if (post != null) {
//                post.releaseConnection();
//            }
//        }
//    }
//
//    /**
//     * @see http://hc.apache.org/httpcomponents-client-4.3.x/examples.html
//     * @param url
//     * @param entity
//     * @param timeoutMills
//     * @return
//     * @throws ClientProtocolException
//     * @throws IOException
//     */
//    public HttpResult post(String url, Map<String, Object> entity, int timeoutMills)
//            throws ClientProtocolException, IOException {
//        return post(url, null, entity, timeoutMills);
//    }
//
//    /**
//     * @see http://hc.apache.org/httpcomponents-client-4.3.x/examples.html
//     * @param url
//     * @param entity
//     * @return
//     * @throws ClientProtocolException
//     * @throws IOException
//     */
//    public HttpResult post(String url, Map<String, Object> entity) throws ClientProtocolException, IOException {
//        return post(url, null, entity, DEFAULT_TIME_OUT);
//    }

   public HttpResult head(String url, int timeoutMills) throws ClientProtocolException,
   IOException {
HttpHead head = null;
try {
   head = new HttpHead(url);
   if (MapUtils.isNotEmpty(headers)) {
       List<Header> list = new LinkedList<Header>();
       for (Map.Entry<String, Object> entry : headers.entrySet()) {
           Header header = new BasicHeader(entry.getKey(),
                   ObjectUtils.toString(entry.getValue()));
           list.add(header);
       }
       head.setHeaders(list.toArray(new Header[list.size()]));
   }
   head.setConfig(initConfig(timeoutMills));
   return httpclient.execute(head, handler);
} finally {
   if (head != null) {
       head.releaseConnection();
   }
}
}

public HttpResult head(String url) throws ClientProtocolException,
   IOException {
return head(url, DEFAULT_TIME_OUT);
}
   
//    public HttpResult head(String url, int timeoutMills) throws ClientProtocolException, IOException {
//        return head(url, timeoutMills, null);
//    }
//
//    public HttpResult head(String url, int timeoutMills, Map<String, Object> headers)
//            throws ClientProtocolException, IOException {
//        HttpHead head = null;
//        try {
//            head = new HttpHead(url);
//            if (MapUtils.isNotEmpty(headers)) {
//                List<Header> list = new LinkedList<Header>();
//                for (Map.Entry<String, Object> entry : headers.entrySet()) {
//                    Header header = new BasicHeader(entry.getKey(), ObjectUtils.toString(entry.getValue()));
//                    list.add(header);
//                }
//                head.setHeaders(list.toArray(new Header[list.size()]));
//            }
//            head.setConfig(initConfig(timeoutMills));
//            return httpclient.execute(head, handler);
//        } finally {
//            if (head != null) {
//                head.releaseConnection();
//            }
//        }
//    }
//
//    public HttpResult head(String url) throws ClientProtocolException, IOException {
//        return head(url, DEFAULT_TIME_OUT, null);
//    }

    private RequestConfig initConfig(int timeOutMills) {

        Builder builder = RequestConfig.custom();
        builder.setSocketTimeout(timeOutMills).setConnectTimeout(timeOutMills)
                .setConnectionRequestTimeout(timeOutMills);

        if (proxy != null) {
            builder.setProxy(proxy);
        }
        return builder.build();
    }

//    private RequestConfig initConfig(int timeOutMills, HttpHost proxy) {
//        return RequestConfig.custom().setSocketTimeout(timeOutMills).setConnectTimeout(timeOutMills)
//                .setConnectionRequestTimeout(timeOutMills).setProxy(proxy).build();
//    }

    public void shutdown() {
        connManager.shutdown();
    }

    private static class Handler implements ResponseHandler<HttpResult> {

        public HttpResult handleResponse(HttpResponse response) throws ClientProtocolException, IOException {

            StatusLine statusLine = response.getStatusLine();
            HttpEntity entity = response.getEntity();
            HttpResult ret = new HttpResult();
            ret.setStatus(statusLine.getStatusCode());
            ret.setHeaders(convert(response.getAllHeaders()));
            ret.setContent(entity == null ? null : EntityUtils.toByteArray(entity));
            return ret;
        }

        private List<NameValuePair> convert(Header[] headers) {

            if (ArrayUtils.isEmpty(headers)) {
                return Collections.emptyList();
            }

            List<NameValuePair> ret = new ArrayList<NameValuePair>();
            for (Header header : headers) {
                NameValuePair pair = new BasicNameValuePair(header.getName(), header.getValue());
                ret.add(pair);
            }
            return ret;
        }

    }

    private static class ResponseInterceptor implements HttpResponseInterceptor {

        public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
            HttpEntity entity = response.getEntity();
            if (entity == null) {
                return;
            }
            Header header = entity.getContentEncoding();
            if (header == null) {
                return;
            }
            HeaderElement[] elements = header.getElements();
            if (ArrayUtils.isEmpty(elements)) {
                return;
            }
            for (HeaderElement element : elements) {
                if (StringUtils.equalsIgnoreCase("gzip", element.getName())) {
                    response.setEntity(new GzipDecompressingEntity(response.getEntity()));
                    return;
                }
            }
        }

    }

    public void setProxy(String proxyAddr, int proxyPort) {
        proxy = new HttpHost(proxyAddr, proxyPort);
    }

    public void setHeaders(Map<String, Object> headers) {
        this.headers = headers;
    }

    public void setParams(Map<String, Object> params) {
        this.entity = params;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }
}
